import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, QueryBuilder, Repository, SelectQueryBuilder } from 'typeorm';
import { EvaluationModel } from './evaluation.model';
import {
  ArticleStatus,
  ArticleType,
  EvaluationCategory,
  EvaluationStatus,
  MASTER_USER_UUID,
  SpecificationValueType
} from "common/utils/data";
import {
  EvaluationAdminCreateDto,
  EvaluationAdminIndexDto,
  EvaluationAdminUpdateDto,
  EvaluationBatchDto,
  EvaluationBatchTitleDto,
  EvaluationIndexDto,
  EvaluationOpenApiIndexDto
} from "@/module/evaluation/evaluation.dto";
import { EvaluationDataService } from "@/module/evaluation-data/evaluation-data.service";
import { EvaluationProjectModel } from "@/module/evaluation-project/evaluation-project.model";
import {
  EvaluationAdminShowResult,
  EvaluationBatchTitleResult,
  EvaluationIndexResult,
  EvaluationShowResult
} from "@/module/evaluation/evaluation.result";
import { EvaluationComposeModel } from "@/module/evaluation-compose/evaluation-compose.model";
import { EvaluationComposeService } from "@/module/evaluation-compose/evaluation-compose.service";
import { BizFailedException } from "@/common/biz-failed/biz-failed.exception";
import { BizFailedCodeEnum } from "@/common/biz-failed/biz-failed.enum";
import { Transactional } from "typeorm-transactional";
import { omitUndefined } from "@/common/utils/utils";
import { ArticleModel } from "@/module/article/article.model";
import { EvaluationDataModel } from "@/module/evaluation-data/evaluation-data.model";
import { EvaluationSpecificationModel } from "@/module/evaluation-specification/evaluation-specification.model";
import { SpecificationModel } from "@/module/specification/specification.model";
import { EvaluationFileModel } from "@/module/evaluation-file/evaluation-file.model";
import { XlsxService } from "@/module/xlsx/xlsx.service";
import { intersection } from 'lodash';
import { EQModel } from '../eq/eq.model';
import { Point } from '../eq/eq.service';
import { getFrequencyPoints } from 'common/utils/eq-filter/utils';
import { FilterOptions } from 'common/utils/eq-filter/types';
import { EQShowResult } from '../eq/eq.result';

@Injectable()
export class EvaluationService {
  constructor(
    @InjectRepository(EvaluationModel)
    private readonly evaluationModelRepository: Repository<EvaluationModel>,
    private readonly evaluationDataService: EvaluationDataService,
    private readonly evaluationComposeService: EvaluationComposeService,
    private readonly xlsxService: XlsxService,
    @InjectRepository(EvaluationProjectModel)
    private readonly evaluationProjectModelRepository: Repository<EvaluationProjectModel>,
    @InjectRepository(EvaluationComposeModel)
    private readonly evaluationComposeModelRepository: Repository<EvaluationComposeModel>,
    @InjectRepository(EQModel)
    private readonly eqModelRepository: Repository<EQModel>,
    @InjectRepository(ArticleModel)
    private readonly articleModelRepository: Repository<ArticleModel>,
    @InjectRepository(EvaluationDataModel)
    private readonly evaluationDataModelRepository: Repository<EvaluationDataModel>,
    @InjectRepository(EvaluationSpecificationModel)
    private readonly evaluationSpecificationModelRepository: Repository<EvaluationSpecificationModel>,
    @InjectRepository(EvaluationFileModel)
    private readonly evaluationFileModelRepository: Repository<EvaluationFileModel>,
    @InjectRepository(SpecificationModel)
    private readonly specificationModelRepository: Repository<SpecificationModel>,
  ) { }

  indexQbSelect() {
    return this.evaluationModelRepository.createQueryBuilder('evaluation')
      .leftJoinAndSelect('evaluation.brand', 'brand')
      .leftJoinAndSelect('evaluation.article', 'article')
      .leftJoinAndSelect('evaluation.specifications', 'specifications')
      .leftJoinAndSelect('evaluation.datas', 'datas')
      .select(
        [
          'evaluation.uuid',
          'evaluation.title',
          'evaluation.category',
          'evaluation.price',
          'evaluation.buyUrl',
          'evaluation.createTime',
          'evaluation.updateTime',
          'article.uuid',
          'article.keywords',
          'article.desc',
          'article.thumbnails',
          'article.publishTime',
          'article.isStick',
          'brand.uuid',
          'brand.title',
          'specifications.id',
          'specifications.uuid',
          'specifications.value',
          'specifications.specificationUuid',
          'datas.id',
          'datas.uuid',
          'datas.group',
        ]
      )
      .andWhere(`(evaluation.status = '${EvaluationStatus.PUBLISH}')`)
  }

  async index(
    query: EvaluationIndexDto,
  ) {
    const queryBuilder = this.indexQbSelect()

    if (query.keyword) {
      queryBuilder.andWhere('(evaluation.title ILIKE :keyword OR brand.title ILIKE :keyword)', {
        keyword: `%${query.keyword}%`,
      });
    }

    if (query.brandUuids?.length) {
      queryBuilder.andWhere('(evaluation.brandUuid IN (:...brandUuids))', {
        brandUuids: query.brandUuids,
      });
    }

    if (query.uuids) {
      queryBuilder.andWhere('(evaluation.uuid IN (:...uuids))', {
        uuids: query.uuids.split(','),
      });
    }

    if (query.category) {
      queryBuilder.andWhere('(evaluation.category = :category)', {
        category: query.category,
      });
    }

    if (query.prices?.length) {
      const qb: string[] = [];
      query.prices.forEach(price => {
        const subQb: string[] = [];
        if(price[0] === null && price[1] === null){
          subQb.push(`evaluation.price IS NULL`)
        }else{
          if (price[0]) {
            subQb.push(`evaluation.price >= ${price[0]}`)
          }
          if (price[1]) {
            subQb.push(`evaluation.price <= ${price[1]}`)
          }
        }

        qb.push(`(${subQb.join(' AND ')})`)
      })

      if (qb.length) {
        queryBuilder.andWhere(`(${qb.join(' OR ')})`);
      }
    }

    if (query.specifications?.length) {
      // 使用子查询确保同一个评测满足所有规格条件
      const presetSpecificationList = await this.specificationModelRepository.find();
      const presetSpecificationMap: Record<string, SpecificationModel> = presetSpecificationList.reduce((obj, item) => {
        return {
          ...obj,
          [item.uuid]: item,
        }
      }, {});

      const res = await Promise.all(query.specifications.map(specification => {
        const valueType = presetSpecificationMap[specification.specificationUuid]?.valueType;
        if (valueType && specification.value.length) {
          const subQueryBuilder = this.evaluationSpecificationModelRepository
            .createQueryBuilder('subSpec')
            .select('subSpec.evaluationUuid')
            .where(`subSpec.specificationUuid = :specificationUuid`, {
              specificationUuid: specification.specificationUuid,
            });

          let getZeroValuePromise: () => Promise<EvaluationModel[]> = () => Promise.resolve([]);

          if (valueType === SpecificationValueType.SELECT) {
            subQueryBuilder.andWhere(`(${specification.value.map(valueItem => {
              return `subSpec.value LIKE '%;${valueItem};%'`;
            }).join(' OR ')})`);
          } else if (valueType === SpecificationValueType.NUMBER) {
            const arr = [];
            if (specification.value[0] !== null && specification.value[0] !== undefined) {
              arr.push(`subSpec.numberValue >= ${specification.value[0]}`);
            }
            if (specification.value[1] !== null && specification.value[1] !== undefined) {
              arr.push(`subSpec.numberValue <= ${specification.value[1]}`);
            }
            subQueryBuilder.andWhere(`(${arr.join(' AND ')})`);

            if (specification.value[0] === 0 ||
              ((specification.value[0] === null || specification.value[0] === undefined) && specification.value[1] === 0)
            ) {
              getZeroValuePromise = () => this.evaluationModelRepository
                .createQueryBuilder('evaluation')
                .leftJoin('evaluation.specifications', 'subSpec', `subSpec.specificationUuid = :specificationUuid`, {
                  specificationUuid: specification.specificationUuid,
                })
                .where(`evaluation.status = '${EvaluationStatus.PUBLISH}'`)
                .andWhere('subSpec.id IS NULL')
                .select('evaluation.uuid')
                .getMany();
            }

          }

          return Promise.all([
            subQueryBuilder.getMany(),
            getZeroValuePromise(),
          ]).then(([
            list,
            zeroList,
          ]) => list.map(item => item.evaluationUuid).concat(zeroList.map(item => item.uuid)));
        }

        return Promise.resolve([]);
      }))

      // let noSpecEvaluationUuid: string[] = [];
      // if (!query.lastKey || !query.lastId) {
      //   const cloneQuery = queryBuilder.clone();
      //   noSpecEvaluationUuid = await cloneQuery
      //   .andWhere('(specifications.id IS NULL)')
      //   .select([
      //     'evaluation.uuid',
      //   ]).getMany().then(list=>list.map(item=>item.uuid));
      // }

      const uuids = intersection(...res);

      if (uuids.length) {
        queryBuilder.andWhere(`(evaluation.uuid IN (:...uuids) OR specifications.id IS NULL)`, {
          uuids,
        });
      } else {
        return [
          [],
          0,
        ] as [EvaluationIndexResult[], number]
      }
    }

    query.wrapQueryBuilder(queryBuilder, 'evaluation', {
      firstOrderBy: (origin) => {
        return {
          'article.isStick': 'DESC',
          ...origin,
        }
      }
    })

    const [evaluation, count] = await queryBuilder.getManyAndCount();

    return [
      evaluation.map(item => {
        const { datas, ...restItem } = item;
        return {
          ...restItem,
          dataGroups: this.evaluationDataService.getEvaluationDataGroups(datas),
        }
      }),
      count,
    ] as [EvaluationIndexResult[], number];
  }

  async show(uuid: string) {
    const evaluation = await this.evaluationModelRepository.findOne({
      where: {
        uuid,
        status: EvaluationStatus.PUBLISH,
      },
    });
    if (!evaluation) {
      throw new BizFailedException(BizFailedCodeEnum.EVALUATION_NOT_EXISTS);
    }

    const res = (await this.evaluationModelRepository
      .createQueryBuilder('evaluation')
      .where(`evaluation.status = '${EvaluationStatus.PUBLISH}'`)
      .andWhere('evaluation.uuid = :uuid', { uuid })
      .leftJoinAndSelect('evaluation.article', 'article')
      .leftJoinAndSelect('evaluation.specifications', 'specifications')
      .leftJoinAndSelect('evaluation.brand', 'brand')
      .leftJoinAndSelect('evaluation.datas', 'datas')
      .leftJoinAndSelect('datas.project', 'project')
      .select([
        'evaluation.uuid',
        'evaluation.title',
        'evaluation.buyUrl',
        'evaluation.category',
        'evaluation.price',
        'article.uuid',
        'article.keywords',
        'article.desc',
        'article.thumbnails',
        'article.publishTime',
        'brand.uuid',
        'brand.title',
        'brand.img',
        'specifications.uuid',
        'specifications.value',
        'specifications.specificationUuid',
        'datas.uuid',
        'datas.data',
        'datas.defaultShow',
        'datas.img',
        'datas.json',
        'datas.desc',
        'datas.name',
        'datas.group',
        'datas.sort',
        'datas.updateTime',
        'datas.projectUuid',
        'project.uuid',
        'project.name',
        'project.desc',
        'project.rule',
        'project.type',
        'project.sort',
      ])
      .getOne())!;

    const currentRes: EvaluationShowResult = {
      ...res,
      datas: this.evaluationDataService.getEvaluationDatasBySourceEvaluationDataModel(res.datas)
    };
    return this.handleSecretArticle(currentRes);
  }

  async batch(query: EvaluationBatchDto) {
    const projects = await this.evaluationProjectModelRepository.find({
      select: [
        'uuid',
        'name',
        'desc',
        'rule',
        'type',
        'code',
      ]
    });

    const res: {
      evaluations: EvaluationModel[];
      evaluationComposes: EvaluationComposeModel[];
      eqs: EQShowResult[];
    } = {
      evaluations: [],
      evaluationComposes: [],
      eqs: [],
    }

    if (query.evaluationUuids?.length) {
      res.evaluations = await this.evaluationModelRepository
        .createQueryBuilder('evaluation')
        .leftJoinAndSelect('evaluation.datas', 'datas')
        .andWhere('(evaluation.uuid In (:...evaluationUuids))', {
          evaluationUuids: query.evaluationUuids,
        })
        .andWhere(`(evaluation.status = '${EvaluationStatus.PUBLISH}')`)
        .select([
          'evaluation.uuid',
          'evaluation.title',
          'datas.uuid',
          'datas.data',
          'datas.img',
          'datas.json',
          'datas.desc',
          'datas.name',
          'datas.group',
          'datas.defaultShow',
          'datas.updateTime',
          'datas.projectUuid',
        ])
        /*.orderBy(`CASE article.uuid
          ${query.articleUuids.split(',').map((item, index)=>`WHEN '${item}' THEN ${index + 1}`).join(' ')}
          END`, 'ASC')*/
        .addOrderBy('datas.sort', 'ASC')
        .getMany();
    }

    if (query.evaluationComposeUuids?.length) {
      res.evaluationComposes = await this.evaluationComposeModelRepository
        .createQueryBuilder('evaluationCompose')
        .andWhere('(evaluationCompose.uuid In (:...evaluationComposeUuids))', {
          evaluationComposeUuids: query.evaluationComposeUuids,
        })
        .leftJoinAndSelect('evaluationCompose.earbudsEvaluation', 'earbudsEvaluation', `(earbudsEvaluation.status = '${EvaluationStatus.PUBLISH}')`)
        .leftJoinAndSelect('evaluationCompose.soundSourceEvaluation', 'soundSourceEvaluation', `(soundSourceEvaluation.status = '${EvaluationStatus.PUBLISH}')`)

        .leftJoinAndSelect('earbudsEvaluation.brand', 'earbudsEvaluationBrand')
        .leftJoinAndSelect('earbudsEvaluation.datas', 'earbudsEvaluationDatas')
        .leftJoinAndSelect('soundSourceEvaluation.brand', 'soundSourceEvaluationBrand')
        .leftJoinAndSelect('soundSourceEvaluation.datas', 'soundSourceEvaluationDatas')
        .select([
          'evaluationCompose.uuid',
          'evaluationCompose.title',
          'evaluationCompose.earbudsEvaluationUuid',
          'evaluationCompose.earbudsEvaluationDataGroup',
          'evaluationCompose.soundSourceEvaluationUuid',
          'evaluationCompose.soundSourceEvaluationDataGroup',
          'evaluationCompose.updateTime',
          'earbudsEvaluation.title',
          'soundSourceEvaluation.title',

          'earbudsEvaluationBrand.title',
          'earbudsEvaluationBrand.img',
          'soundSourceEvaluationBrand.title',
          'soundSourceEvaluationBrand.img',

          'earbudsEvaluationDatas.uuid',
          'earbudsEvaluationDatas.data',
          'earbudsEvaluationDatas.defaultShow',
          'earbudsEvaluationDatas.img',
          'earbudsEvaluationDatas.json',
          'earbudsEvaluationDatas.desc',
          'earbudsEvaluationDatas.name',
          'earbudsEvaluationDatas.group',
          'earbudsEvaluationDatas.sort',
          'earbudsEvaluationDatas.updateTime',
          'earbudsEvaluationDatas.projectUuid',

          'soundSourceEvaluationDatas.uuid',
          'soundSourceEvaluationDatas.data',
          'soundSourceEvaluationDatas.defaultShow',
          'soundSourceEvaluationDatas.img',
          'soundSourceEvaluationDatas.json',
          'soundSourceEvaluationDatas.desc',
          'soundSourceEvaluationDatas.name',
          'soundSourceEvaluationDatas.group',
          'soundSourceEvaluationDatas.sort',
          'soundSourceEvaluationDatas.updateTime',
          'soundSourceEvaluationDatas.projectUuid',
        ])
        .getMany();
    }

    let eqs: (EQShowResult | null)[] = [];
    if (query.eqUuids?.length) { // TODO 因为循环依赖卡主了，后面优化一下
      eqs = await Promise.all(query.eqUuids.map(async (item) => {
        const eq = await this.eqModelRepository.findOne({
          where: {
            uuid: item,
          },
          select: {
            id: true,
            uuid: true,
            evaluationUuid: true,
            dataGroup: true,
            name: true,
            audioFiles: true,
            filters: true,
            createTime: true,
            updateTime: true,
          },
        });

        if (!eq) {
          return null;
        }

        const evaluation = await this.show(eq.evaluationUuid);
        // 过滤出所选择的模式的频响数据
        const freqDbaDataList = evaluation.datas.find(
          (i) => i.project.uuid === 'cdcf35b6-f37c-4b77-877f-9b8ddbf38c5b',
        );
        if (!freqDbaDataList) {
          return null
        }

        const freqDbaData = freqDbaDataList.children.find(
          (i) => i.group === eq.dataGroup,
        );
        if (!freqDbaData) {
          return null;
        }

        // 转换频响数据
        const sourceData = freqDbaData.dataSet || [];
        const headphonesPoints: Point[] = [];

        for (let j = 1; j < sourceData.length; j++) {
          const item = sourceData[j];
          if (item && item.length > 1) {
            headphonesPoints.push({
              x: Number(item[0]),
              y: Number(item[1]),
            });
          }
        }

        const points = getFrequencyPoints(
          headphonesPoints,
          eq.filters as FilterOptions[],
        ).map((i) => [i.x, i.y]);

        return {
          ...eq,
          points,
        };
      }));

    }

    return projects.map(project => {
      const list = res.evaluations.map(evaluation => {
        return {
          title: evaluation.title,
          datas: evaluation.datas.filter(item => item.projectUuid === project.uuid).map(item => {
            return {
              dataSet: this.evaluationDataService.getEvaluationDataSet(item.data),
              group: item.group,
              defaultShow: item.defaultShow,
              desc: item.desc,
              img: item.img,
              json: item.json,
              name: item.name,
              projectUuid: item.projectUuid,
              updateTime: item.updateTime,
            }
          })
        }
      }).concat(res.evaluationComposes.map(evaluationCompose => {
        return {
          title: evaluationCompose.title,
          datas: evaluationCompose.earbudsEvaluation.datas.filter(item => item.projectUuid === project.uuid).map(earbudsEvaluationDataItem => {
            const param: Parameters<typeof this.evaluationComposeService.getEvaluationComposeDataSet>[0] = {
              earbudsEvaluationDataSet: this.evaluationDataService.getEvaluationDataSet(earbudsEvaluationDataItem.data),
              projectCode: project.code
            };

            if (evaluationCompose.soundSourceEvaluation) {
              const matchSoundSourceEvaluationSourceData = evaluationCompose.soundSourceEvaluation.datas.find(soundSourceEvaluationDataItem => soundSourceEvaluationDataItem.projectUuid === earbudsEvaluationDataItem.projectUuid);
              if (matchSoundSourceEvaluationSourceData) {
                param.soundSourceEvaluationDataSet = this.evaluationDataService.getEvaluationDataSet(matchSoundSourceEvaluationSourceData.data)
              }
            }

            return {
              dataSet: this.evaluationComposeService.getEvaluationComposeDataSet(param),
              group: earbudsEvaluationDataItem.group,
              defaultShow: earbudsEvaluationDataItem.defaultShow,
              desc: earbudsEvaluationDataItem.desc,
              img: earbudsEvaluationDataItem.img,
              json: earbudsEvaluationDataItem.json,
              name: earbudsEvaluationDataItem.name,
              projectUuid: earbudsEvaluationDataItem.projectUuid,
              updateTime: earbudsEvaluationDataItem.updateTime,
            }
          })
        }
      }));
      if (project.uuid === 'cdcf35b6-f37c-4b77-877f-9b8ddbf38c5b') {
        list.push(...eqs.filter(item => item).map((item) => {
          const eq = item as EQShowResult;
          const dataSet = eq.points.map(item => {
            return [item[0].toString(), item[1].toString()];
          });
          dataSet.unshift(['Freq(Hz)', 'SPL(dB)']);
          return {
            title: eq.name,
            datas: [
              {
                defaultShow: 1,
                desc: '',
                img: '',
                json: {},
                name: '',
                projectUuid: 'cdcf35b6-f37c-4b77-877f-9b8ddbf38c5b',
                updateTime: Date.now(),
                uuid: eq.uuid,
                group: eq.dataGroup,
                dataSet,
              }
            ]
          }
        }))
      }
      return {
        project,
        list,
      }
    }).map(project => {
      return {
        ...project,
        list: project.list.filter(item => item.datas.length)
      }
    })
  }

  async batchTitle(query: EvaluationBatchTitleDto) {
    const res: EvaluationBatchTitleResult[] = [];

    if (query.evaluationUuids) {
      const evaluations = await this.evaluationModelRepository
        .createQueryBuilder('evaluation')
        .select([
          'evaluation.uuid',
          'evaluation.title',
        ])
        .andWhere(`(evaluation.status = '${EvaluationStatus.PUBLISH}')`)
        .andWhere(`(evaluation.uuid IN (:...uuids))`, {
          uuids: query.evaluationUuids.split(','),
        }).getMany();
      res.push(...evaluations.map(item => {
        return {
          ...item,
          belong: 'evaluation'
        }
      }))
    }

    if (query.evaluationComposeUuids) {
      const evaluationComposes = await this.evaluationComposeModelRepository
        .createQueryBuilder('evaluationCompose')
        .select([
          'evaluationCompose.uuid',
          'evaluationCompose.title',
        ])
        .andWhere(`(evaluationCompose.uuid IN (:...uuids))`, {
          uuids: query.evaluationComposeUuids.split(','),
        }).getMany();

      res.push(...evaluationComposes.map(item => {
        return {
          ...item,
          belong: 'evaluationCompose'
        }
      }))
    }

    if (query.eqUuids) {
      const eqs = await this.eqModelRepository
        .createQueryBuilder('eq')
        .select([
          'eq.uuid',
          'eq.name',
        ])
        .andWhere(`(eq.uuid IN (:...uuids))`, {
          uuids: query.eqUuids.split(','),
        }).getMany();

      res.push(...eqs.map(item => {
        return {
          uuid: item.uuid,
          title: item.name,
          belong: 'eq'
        }
      }))
    }

    return res;
  }


  async adminIndex(query: EvaluationAdminIndexDto) {
    const qb = this.evaluationModelRepository
      .createQueryBuilder('evaluation')
      .leftJoinAndSelect('evaluation.article', 'article')
      .leftJoinAndSelect('evaluation.brand', 'brand')
      .select([
        'evaluation.uuid',
        'evaluation.title',
        'evaluation.category',
        'evaluation.status',
        'article.uuid',
        'article.isStick',
        'article.thumbnails',
        'article.keywords',
        'article.status',
        'article.desc',
        'article.publishTime',
        'article.createTime',
        'article.updateTime',
        'brand.uuid',
        'brand.title',
      ])
      .limit(query.getPageSize())
      .offset(query.getOffset())
      .orderBy({
        'article.isStick': 'DESC',
        'article.publishTime': 'DESC',
        'article.id': 'DESC',
      })

    if (query.status) {
      qb.andWhere('(evaluation.status = :status)', { status: query.status });
    }

    if (query.category) {
      qb.andWhere('(evaluation.category = :category)', {
        category: query.category,
      });
    }

    if (query.title) {
      qb.andWhere('(evaluation.title LIKE :title)', { title: `%${query.title}%` });
    }

    if (query.brandUuids) {
      qb.andWhere('(evaluation.brandUuid IN (:...brandUuids))', { brandUuids: query.brandUuids.split(',') });
    }

    return qb.getManyAndCount();
  }


  async adminShow(uuid: string) {
    const evaluation = await this.evaluationModelRepository.findOne({
      where: {
        uuid,
      },
    });
    if (!evaluation) {
      throw new BizFailedException(BizFailedCodeEnum.EVALUATION_NOT_EXISTS);
    }
    const res = (await this.evaluationModelRepository
      .createQueryBuilder('evaluation')
      .leftJoinAndSelect('evaluation.article', 'article')
      .leftJoinAndSelect('evaluation.brand', 'brand')
      .leftJoinAndSelect('evaluation.datas', 'datas')
      .leftJoinAndSelect('evaluation.specifications', 'specifications')
      .leftJoinAndSelect('evaluation.impulseResponseFiles', 'impulseResponseFiles')
      .leftJoinAndSelect('datas.project', 'project')
      .select([
        'evaluation.uuid',
        'evaluation.title',
        'evaluation.category',
        'evaluation.brandUuid',
        'evaluation.price',
        'evaluation.buyUrl',
        'evaluation.status',
        'article.uuid',
        'article.keywords',
        'article.desc',
        'article.thumbnails',
        'article.publishTime',
        'article.status',
        'article.type',
        'brand.uuid',
        'brand.title',
        'datas.uuid',
        'datas.data',
        'datas.defaultShow',
        'datas.img',
        'datas.json',
        'datas.desc',
        'datas.sort',
        'datas.name',
        'datas.group',
        'datas.projectUuid',
        'specifications.uuid',
        'specifications.specificationUuid',
        'specifications.value',
        'impulseResponseFiles.uuid',
        'impulseResponseFiles.group',
        'impulseResponseFiles.url',
        'project.uuid',
        'project.name',
        'project.desc',
        'project.rule',
        'project.type',
        'project.sort',
      ])
      .andWhere('evaluation.uuid = :uuid', { uuid })
      .getOne())!;


    const currentRes: EvaluationAdminShowResult = {
      ...res,
      datas: res.datas
        .sort((a, b) => {
          return a.project.sort - b.project.sort || a.sort - b.sort;
        })
        .map((item) => {
          return {
            ...item,
            dataSet: this.evaluationDataService.getEvaluationDataSet(item.data),
          };
        }),
      showDatas: this.evaluationDataService.getEvaluationDatasBySourceEvaluationDataModel(res.datas),
      dataGroups: this.evaluationDataService.getEvaluationDataGroups(res.datas),
      impulseResponseFiles: res.impulseResponseFiles.sort((a, b) => {
        return a.sort - b.sort;
      })
    };
    return currentRes;
  }

  @Transactional()
  async adminCreate(
    body: EvaluationAdminCreateDto,
  ): Promise<void> {
    const {
      datas,
      specifications,
      brandUuid,
      price,
      buyUrl,
      impulseResponseFiles,
      category,
      ...restBody
    } = body;
    const article = await this.articleModelRepository.save({
      ...restBody,
      userUuid: MASTER_USER_UUID,
      type: ArticleType.EVALUATION,
      status: ArticleStatus.REVIEW,
    });
    const evaluation = await this.evaluationModelRepository.save({
      articleUuid: article.uuid,
      brandUuid,
      price,
      buyUrl,
      category,
      title: restBody.title,
      status: EvaluationStatus.UNPUBLISH,
    })
    await Promise.all([
      this.evaluationDataModelRepository.save(
        datas.map((item, index) => {
          return {
            ...item,
            evaluationUuid: evaluation.uuid,
            sort: index,
          };
        }),
      ),
      ...(specifications?.length ? [
        this.evaluationSpecificationModelRepository.save(
          specifications.map((item, index) => {
            return {
              ...item,
              evaluationUuid: evaluation.uuid,
              sort: index,
              numberValue: isNaN(Number(item.value)) ? null : Number(item.value),
            };
          }),
        )
      ] : []),
      ...(impulseResponseFiles?.length ? [
        this.evaluationFileModelRepository.save(
          impulseResponseFiles.map((item, index) => {
            return {
              ...item,
              evaluationUuid: evaluation.uuid,
              sort: index,
            };
          }),
        )
      ] : [])
    ])
  }

  @Transactional()
  async adminUpdate(
    body: EvaluationAdminUpdateDto,
    uuid: string,
  ): Promise<void> {
    const evaluation = await this.evaluationModelRepository.findOne({
      where: {
        uuid,
      },
    });
    if (!evaluation) {
      throw new BizFailedException(BizFailedCodeEnum.EVALUATION_NOT_EXISTS);
    }
    const {
      datas,
      specifications,
      brandUuid,
      price,
      buyUrl,
      impulseResponseFiles,
      category,
      title,
      ...restBody
    } = body;
    const param: Omit<EvaluationAdminUpdateDto, 'datas' | 'specifications' | 'brandUuid' | 'buyUrl' | 'price'> & {
      publishTime?: number;
    } = restBody;
    if (
      evaluation.status !== EvaluationStatus.PUBLISH &&
      body.status === EvaluationStatus.PUBLISH
    ) {
      param.publishTime = Date.now();
    }
    if (Object.keys(param).length) {
      await this.articleModelRepository.update({
        uuid: evaluation.articleUuid,
      },
        omitUndefined(param)
      )
    }
    await this.evaluationModelRepository.update({
      uuid: evaluation.uuid,
    },
      omitUndefined({
        brandUuid,
        price,
        buyUrl,
        status: body.status,
        category,
        title,
      })
    )
    if (datas) {
      await Promise.all([
        this.evaluationDataModelRepository.delete({
          evaluationUuid: evaluation.uuid,
        }),
        this.evaluationDataModelRepository.save(
          datas.map((item, index) => {
            return {
              ...item,
              evaluationUuid: evaluation.uuid,
              sort: index,
            };
          }),
        ),
      ]);
    }
    if (specifications) {
      await Promise.all([
        this.evaluationSpecificationModelRepository.delete({
          evaluationUuid: evaluation.uuid,
        }),
        this.evaluationSpecificationModelRepository.save(
          specifications.map((item, index) => {
            return {
              ...item,
              evaluationUuid: evaluation.uuid,
              sort: index,
              numberValue: isNaN(Number(item.value)) ? null : Number(item.value),
            };
          }),
        ),
      ]);
    }
    if (impulseResponseFiles) {
      await Promise.all([
        this.evaluationFileModelRepository.delete({
          evaluationUuid: evaluation.uuid,
        }),
        this.evaluationFileModelRepository.save(
          impulseResponseFiles.map((item, index) => {
            return {
              ...item,
              evaluationUuid: evaluation.uuid,
              sort: index,
            };
          }),
        ),
      ]);
    }
  }

  async openapiIndex(
    query: EvaluationOpenApiIndexDto,
  ) {
    const queryBuilder = this.indexQbSelect()

    if (query.keyword) {
      queryBuilder.andWhere('(evaluation.title ILIKE :keyword OR brand.title ILIKE :keyword)', {
        keyword: `%${query.keyword}%`,
      });
    }

    if (query.brandUuids?.length) {
      queryBuilder.andWhere('(evaluation.brandUuid IN (:...brandUuids))', {
        brandUuids: query.brandUuids,
      });
    }

    if (query.uuids?.length) {
      queryBuilder.andWhere('(evaluation.uuid IN (:...uuids))', {
        uuids: query.uuids,
      });
    }

    if (query.category) {
      queryBuilder.andWhere('(evaluation.category = :category)', {
        category: query.category,
      });
    }

    if (query.prices?.length) {
      const qb: string[] = [];
      query.prices.forEach(price => {
        const subQb: string[] = [];
        if(price[0] === null && price[1] === null){
          subQb.push(`evaluation.price IS NULL`)
        }else{
          if (price[0]) {
            subQb.push(`evaluation.price >= ${price[0]}`)
          }
          if (price[1]) {
            subQb.push(`evaluation.price <= ${price[1]}`)
          }
        }

        qb.push(`(${subQb.join(' AND ')})`)
      })

      if (qb.length) {
        queryBuilder.andWhere(`(${qb.join(' OR ')})`);
      }
    }

    if (query.specifications?.length) {
      // 使用子查询确保同一个评测满足所有规格条件
      const presetSpecificationList = await this.specificationModelRepository.find();
      const presetSpecificationMap: Record<string, SpecificationModel> = presetSpecificationList.reduce((obj, item) => {
        return {
          ...obj,
          [item.uuid]: item,
        }
      }, {});

      const res = await Promise.all(query.specifications.map(specification => {
        const valueType = presetSpecificationMap[specification.specificationUuid]?.valueType;
        if (valueType && specification.value.length) {
          const subQueryBuilder = this.evaluationSpecificationModelRepository
            .createQueryBuilder('subSpec')
            .select('subSpec.evaluationUuid')
            .where(`subSpec.specificationUuid = :specificationUuid`, {
              specificationUuid: specification.specificationUuid,
            });

          let getZeroValuePromise: () => Promise<EvaluationModel[]> = () => Promise.resolve([]);

          if (valueType === SpecificationValueType.SELECT) {
            subQueryBuilder.andWhere(`(${specification.value.map(valueItem => {
              return `subSpec.value LIKE '%;${valueItem};%'`;
            }).join(' OR ')})`);
          } else if (valueType === SpecificationValueType.NUMBER) {
            const arr = [];
            if (specification.value[0] !== null && specification.value[0] !== undefined) {
              arr.push(`subSpec.numberValue >= ${specification.value[0]}`);
            }
            if (specification.value[1] !== null && specification.value[1] !== undefined) {
              arr.push(`subSpec.numberValue <= ${specification.value[1]}`);
            }
            subQueryBuilder.andWhere(`(${arr.join(' AND ')})`);

            if (specification.value[0] === 0 ||
              ((specification.value[0] === null || specification.value[0] === undefined) && specification.value[1] === 0)
            ) {
              getZeroValuePromise = () => this.evaluationModelRepository
                .createQueryBuilder('evaluation')
                .leftJoin('evaluation.specifications', 'subSpec', `subSpec.specificationUuid = :specificationUuid`, {
                  specificationUuid: specification.specificationUuid,
                })
                .where(`evaluation.status = '${EvaluationStatus.PUBLISH}'`)
                .andWhere('subSpec.id IS NULL')
                .select('evaluation.uuid')
                .getMany();
            }

          }

          return Promise.all([
            subQueryBuilder.getMany(),
            getZeroValuePromise(),
          ]).then(([
            list,
            zeroList,
          ]) => list.map(item => item.evaluationUuid).concat(zeroList.map(item => item.uuid)));
        }

        return Promise.resolve([]);
      }))

      const uuids = intersection(...res);

      if (uuids.length) {
        queryBuilder.andWhere(`(evaluation.uuid IN (:...uuids) OR specifications.id IS NULL)`, {
          uuids,
        });
      } else {
        return [
          [],
          0,
        ] as [EvaluationIndexResult[], number]
      }
    }

    query.wrapQueryBuilder(queryBuilder, 'evaluation', {
      firstOrderBy: (origin) => {
        return {
          'article.isStick': 'DESC',
          ...origin,
        }
      }
    })

    const [evaluation, count] = await queryBuilder.getManyAndCount();

    return [
      evaluation.map(item => {
        const { datas, ...restItem } = item;
        return {
          ...restItem,
          dataGroups: this.evaluationDataService.getEvaluationDataGroups(datas),
        }
      }),
      count,
    ] as [EvaluationIndexResult[], number];
  }

  // 查询文章时，处理文章是否有保密情况
  private handleSecretArticle<T>(article: T): T {
    return article;
  }

  async exportRawFrData() {
    const project = await this.evaluationProjectModelRepository.findOne({
      where: {
        code: 'RAW_FR'
      }
    })
    if (!project) {
      throw new BizFailedException(BizFailedCodeEnum.EVALUATION_NOT_EXISTS);
    }

    const evaluations = await this.evaluationModelRepository.createQueryBuilder('evaluation')
      .leftJoinAndSelect('evaluation.datas', 'datas')
      .leftJoinAndSelect('evaluation.brand', 'brand')
      .select([
        'evaluation.title',
        'brand.title',
        'datas.data',
        'datas.group',
        'datas.projectUuid',
      ])
      .where(`evaluation.category = '${EvaluationCategory.EARBUDS}'`)
      .andWhere(`datas.projectUuid = '${project.uuid}'`)
      .getMany();

    const allXMap: Record<string, true> = {};

    const list = evaluations.flatMap(evaluation => evaluation.datas.map(data => {
      const dataSet = this.evaluationDataService.getEvaluationDataSet(data.data);
      const dataMap: Record<string, string> = {};
      dataSet.forEach((row, index) => {
        if (index !== 0) {
          dataMap[row[0]] = row[1];
          allXMap[row[0]] = true;
        }
      })
      return {
        title: `【${evaluation.brand.title}】${evaluation.title} - ${data.group}`,
        dataMap,
      }
    }))

    if (!list.length) {
      throw new BizFailedException(BizFailedCodeEnum.EVALUATION_NOT_EXISTS);
    }

    // [{'Freq(Hz)': '20.144928', '【effect audio】朝圣者 - 默认调音模式': '20.1265', '【六月船歌】六月船歌 鲨鱼乐队 - 默认调音模式': '20.1255'}]

    const xList = Object.keys(allXMap);
    const res = xList
      .sort((a, b) => Number(a) - Number(b))
      .map(x => {
        const obj: Record<string, string> = {};
        list.forEach(item => {
          obj[item.title] = item.dataMap[x];
        })
        return {
          'Freq(Hz)': x,
          ...obj,
        }
      })

    return this.xlsxService.export(
      [
        {
          title: 'Freq(Hz)',
          key: 'Freq(Hz)',
        },
        ...(list.map(item => {
          return {
            title: item.title,
            key: item.title,
          }
        })),
      ],
      res,
    );
  }
}
